﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace PloobsEngine.SceneControl
{
    public class GaussianBlurPostEffect : IPostEffect
    {
        #region IPostEffect Members

        private Effect gblur;
        private bool init = false;
        private RenderTarget2D target;
        private Vector2[] sampleOffsetsH = new Vector2[15];
        private float[] sampleWeightsH = new float[15];
        private SpriteBatch spriteBatch;
        private EngineStuff engine;

        private Vector2[] sampleOffsetsV = new Vector2[15];
        private float[] sampleWeightsV = new float[15];

        private Texture2D intermediateTex;

        void SetBlurParameters(float dx, float dy, ref Vector2[] vSampleOffsets, ref float[] fSampleWeights)
        {
            // The first sample always has a zero offset.
            fSampleWeights[0] = ComputeGaussian(0);
            vSampleOffsets[0] = new Vector2(0);

            // Maintain a sum of all the weighting values.
            float totalWeights = fSampleWeights[0];

            // Add pairs of additional sample taps, positioned
            // along a line in both directions from the center.
            for (int i = 0; i < 15 / 2; i++)
            {
                // Store weights for the positive and negative taps.
                float weight = ComputeGaussian(i + 1);

                fSampleWeights[i * 2 + 1] = weight;
                fSampleWeights[i * 2 + 2] = weight;

                totalWeights += weight * 2;

                // The 1.5 offset kicks things off by
                // positioning us nicely in between two texels.
                float sampleOffset = i * 2 + 1.5f;

                Vector2 delta = new Vector2(dx, dy) * sampleOffset;

                // Store texture coordinate offsets for the positive and negative taps.
                vSampleOffsets[i * 2 + 1] = delta;
                vSampleOffsets[i * 2 + 2] = -delta;
            }

            // Normalize the list of sample weightings, so they will always sum to one.
            for (int i = 0; i < fSampleWeights.Length; i++)
                fSampleWeights[i] /= totalWeights;
        }
        private float ComputeGaussian(float n)
        {
            float theta = 2.0f + float.Epsilon;
            return theta = (float)((1.0 / Math.Sqrt(2 * Math.PI * theta)) * Math.Exp(-(n * n) / (2 * theta * theta)));
        }
        

        // Set blur parameters to effect
        void SetParameters(GaussianBlurDirection Direction)
        {
            if (Direction == GaussianBlurDirection.Horizontal)
            {
                gblur.Parameters["sampleWeights"].SetValue(sampleWeightsH);
                gblur.Parameters["sampleOffsets"].SetValue(sampleOffsetsH);
            }
            else
            {
                gblur.Parameters["sampleWeights"].SetValue(sampleWeightsV);
                gblur.Parameters["sampleOffsets"].SetValue(sampleOffsetsV);
            }
        }



        public void Draw(Texture2D tex, IRenderHelper rHelper,Microsoft.Xna.Framework.GameTime gt, Microsoft.Xna.Framework.Rectangle screenRec, IWorld world)
        {
            if(!init)
            {
                Vector2 texelSize = new Vector2(1f / screenRec.Height, 1f / screenRec.Width);
                SetBlurParameters(texelSize.X, 0, ref sampleOffsetsH, ref sampleWeightsH);
                SetBlurParameters(0, texelSize.Y, ref sampleOffsetsV, ref sampleWeightsV);
                init = true;
            }

            RenderTarget2D rd = EngineStuff.GraphicsDevice.GetRenderTarget(0) as RenderTarget2D;
            EngineStuff.GraphicsDevice.SetRenderTarget(0, target);
            EngineStuff.GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1, 0);

            spriteBatch.Begin(SpriteBlendMode.None, SpriteSortMode.Immediate, SaveStateMode.SaveState);
            {             
                gblur.Begin();
                {
                    gblur.CurrentTechnique.Passes[0].Begin();
                    {
                        SetParameters(GaussianBlurDirection.Horizontal); // Set horizontal parameters
                        spriteBatch.Draw(tex, screenRec, Color.White);
                        gblur.CurrentTechnique.Passes[0].End();
                    }
                }
                gblur.End();
            }
            spriteBatch.End();

            EngineStuff.GraphicsDevice.SetRenderTarget(0, rd);
            //EngineStuff.GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1, 0);
            intermediateTex = target.GetTexture();


            spriteBatch.Begin(SpriteBlendMode.None, SpriteSortMode.Immediate, SaveStateMode.SaveState);
            {
                gblur.Begin();
                {
                    gblur.CurrentTechnique.Passes[0].Begin();
                    {
                        SetParameters(GaussianBlurDirection.Vertical); // Set vertical parameters
                        spriteBatch.Draw(intermediateTex, screenRec, Color.White);
                        gblur.CurrentTechnique.Passes[0].End();
                    }
                }
                gblur.End();
            }
            spriteBatch.End();

            
        }

        

        #endregion

        #region IPostEffect Members

        void IPostEffect.init(EngineStuff engine)
        {
            gblur = EngineStuff.InternalContentManager.GetAsset<Effect>("gblur");
            spriteBatch = EngineStuff.SpriteBatch;
            target = EngineStuff.GetDefaultColorBuffer();
            this.engine = engine;
        }

        #endregion

        #region IPostEffect Members


        public void Draw(IRenderHelper rHelper,  GameTime gt, Rectangle screenRec, IWorld world)
        {
            this.Draw(rHelper[PrincipalConstants.CurrentImage], rHelper,  gt, screenRec, world);
        }

        #endregion
    }
    public enum GaussianBlurDirection { Horizontal, Vertical };
}
